package com.flute.framework.index;

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.flute.framework.FluteMonitor;
import com.flute.framework.LuceneRecords;
import com.flute.framework.StandardDate;
import com.flute.framework.exception.FatalIndexException;
import com.flute.framework.exception.FileBlockException;
import com.flute.framework.exception.IndexException;
import com.flute.framework.filesystem.FileDataBlock;
import com.flute.framework.filesystem.FileLock;
import com.flute.framework.monitor.EventLogger;
import com.flute.framework.monitor.MonitedValue;
import com.flute.framework.util.ftp.LocalFile;
import com.flute.tools.util.FileUtil;

public class IndexFolder {

	private String location;
	private String backupLocation;
	private IFileIndexCounter counter;
	private int backup;

	private FileDataBlock running;
	private FileDataBlock ready;

	/**
	 * 索引操作目录
	 */
	private static final String RUNNING = "running";
	/**
	 * 当前最新索引目录
	 */
	private static final String READY = "ready";

	public IndexFolder(String location) {
		this(location, location);
	}

	public IndexFolder(String location, String backupLocation) {
		this.backup = 5;
		this.location = location;
		this.backupLocation = backupLocation;

		initial();
	}

	private void initial() {
		this.ready = new FileDataBlock(getLocation() + READY, FluteMonitor
				.getIndexerKey());
		if (!ready.existed()) {
			ready.create();
		}
		this.running = new FileDataBlock(getLocation() + RUNNING, FluteMonitor
				.getIndexerKey());
		if (!running.existed()) {
			running.create();
		}
	}

	public void startUpdateIndex() throws IndexException, FatalIndexException {
		startIndex(true);
	}

	public void startRebuildIndex() throws IndexException, FatalIndexException {
		startIndex(false);
	}

	public void startIndex(boolean update) throws IndexException,
			FatalIndexException {
		if (ready == null) {
			initial();
		}

		check(update);

		if (running.startWrite()) {
			info("running创建写锁成功");
		} else {
			error("running创建写锁失败");
		}
	}

	/**
	 * 当新一轮更新开始时，检查两个文件夹所处的状态.
	 * 
	 * 文件夹running没有任何的读取和写入操作。
	 * 
	 * 文件夹ready可能正在被读取，但是没有写入操作。
	 * 
	 * @param update
	 * @throws IndexException
	 * @throws FetalIndexException
	 */
	private void check(boolean update) throws IndexException,
			FatalIndexException {

		// ready正在写入
		info("检查ready文件夹是否遭破坏...");
		if (ready.isWriting() || ready.isClearing()) {
			warn("索引在从running到ready的复制过程中中断.");
			if (running.isEmpty() || running.isWriting()) {
				error("running文件夹为空，或者正在写入，不可能的状况.");
				throw new FatalIndexException("running文件夹为空，或者正在写入，不可能的状态.");
			} else {
				if (!ready.clear()) {
					throw new IndexException("清空ready文件夹'" + getReadyFolder()
							+ "'失败.");
				}
				info("删除running下的读取锁");
				if (!running.removeReadLocks()) {
					throw new IndexException("删除running文件夹'"
							+ getRunningFolder() + "'下读锁失败.");
				}

				try {
					ready.copyFrom(running);
				} catch (FileBlockException e) {
					throw new IndexException(e);
				}
			}
		}
		info("ready文件夹检查结束.");

		// running正在写入
		info("检查running文件夹是否遭破坏...");
		if (running.isWriting() || running.isClearing()) {
			warn("建索引的过程中断,running中索引遭破坏.");
			info("清空running中所有文件...");
			if (!running.clear()) {
				throw new IndexException("清空running文件夹'" + getRunningFolder()
						+ "'失败.");
			}
			info("清空结束.");
		} else if (running.isReading()) {
			warn("索引在从running到ready的复制过程中中断.");
			info("删除running文件夹'" + getRunningFolder() + "'下读锁");
			if (!running.removeReadLocks()) {
				throw new IndexException("删除running文件夹'" + getRunningFolder()
						+ "'下读锁失败.");
			}
		}
		info("running文件夹检查结束.");

		if (update && !ready.isEmpty() && running.isEmpty()) {
			info("更新操作，尝试自我恢复...");
			info("ready文件夹不为空，且ready正常,从ready复制一份索引到running");
			try {
				running.copyFrom(ready);
			} catch (FileBlockException e) {
				throw new IndexException(e);
			}
			info("恢复成功.");
		}

		checkConsistency();

		if (!update) {
			info("重建索引,清空running下所有的文件...");
			if (!running.clear()) {
				throw new IndexException("清空running文件夹'" + getRunningFolder()
						+ "'失败.");
			}
		}
	}

	private void checkConsistency() throws FatalIndexException {
		if (!running.isEmpty() && !ready.isEmpty()) {
			info("ready和running都不为空,检查他们之间是否一致.");

			info("计算路径'" + getReadyFolder() + "'中索引的文档数...");
			int readyCount = counter.countRecord(getReadyFolder());
			info("路径'" + getReadyFolder() + "'中索引的文档数为" + readyCount);

			info("计算路径'" + getRunningFolder() + "'中索引的文档数...");
			int runningCount = counter.countRecord(getRunningFolder());
			info("路径'" + getRunningFolder() + "'中索引的文档数为" + runningCount);

			if (readyCount != runningCount) {
				throw new FatalIndexException("running文件夹中的索引和ready中的索引不一致");
			}
			info("ready和running是一致的.");
		}
	}

	public void endIndex() throws IndexException {

		info("对ready文件夹中索引进行备份...");
		backupReady();

		running.endWrite();

		info("把新建好的索引传输至ready文件夹");
		moveRunningToReady();

		info("把记录数记录在文件'" + LuceneRecords.RECORD_COUNT + "'中.");
		recordCount();
	}

	private void moveRunningToReady() throws IndexException {
		info("检查是否有搜索器正在读取ready文件夹.");

		while (ready.isReading()) {
			info("ready正在被读取，等待5秒...");
			doNothing(5000);
			long time = new Date().getTime();
			for (String lockName : ready.getAllReadFiles()) {
				if (time - new File(ready.getPath() + lockName).lastModified() > 1000 * 3600) {
					warn("在等待了3600s后，读锁文件'" + lockName + "'仍然存在, 强制删除.");
					if (!ready.removeReadLock(lockName)) {
						throw new IndexException("强制删除锁文件'" + lockName + "'失败.");
					}
				}
			}
		}

		try {
			ready.copyFrom(running);
		} catch (FileBlockException e) {
			throw new IndexException(e);
		}
	}

	private void recordCount() throws IndexException {
		// XXX LuceneRecords Constructor
		LuceneRecords lr = new LuceneRecords(ready.getPath(), new LocalFile());
		try {
			lr.setCount(counter.countRecord(ready.getPath()));
		} catch (IOException e) {
			throw new IndexException(e);
		}
	}

	private void backupReady() throws IndexException {
		info("备份ready中的索引...");
		if (!ready.isWriting()) {

			removeOldestFolder();

			String folder = createNewestFolder();

			if (folder != null) {
				try {
					new FileDataBlock(folder, FluteMonitor.getIndexerKey())
							.copyFrom(ready);
				} catch (FileBlockException e) {
					throw new IndexException(e);
				}

			} else {
				warn("创建新的备份文件夹失败.");
			}
		} else {
			throw new IndexException("传输锁存在，正在传输索引到ready文件夹，不能进行备份.");
		}
	}

	private String createNewestFolder() throws IndexException {
		info("创建最新的索引备份文件夹.");
		File[] files = new File(getReadyFolder()).listFiles();
		if (files != null) {
			for (File file : files) {
				// XXX
				if (!FileLock.isLockFile(file.getAbsolutePath(),
						new LocalFile())) {
					String folderName = new StandardDate(file.lastModified())
							.toSpecialDateString();
					File f = new File(getBackupLocation() + folderName);
					int i = 1;
					while (f.exists()) {
						f = new File(getBackupLocation()
								+ new StandardDate(file.lastModified() + i++)
										.toSpecialDateString());
					}
					if (!f.mkdirs()) {
						throw new IndexException("创建新的备份文件夹'"
								+ f.getAbsolutePath() + "'失败.");
					}
					info("创建备份索引文件夹'" + f.getAbsolutePath() + "'.");
					return f.getAbsolutePath() + "/";
				}
			}
		}

		return null;
	}

	/**
	 * 检查location下所有的文件夹，除了ready和running以外
	 * 
	 * 所有的文件夹应该都没有锁文件，如果文件夹以时间命名，则认为是备份索引
	 * 
	 * @return
	 */
	public List<StandardDate> getBackupDates() {
		List<StandardDate> list = new ArrayList<StandardDate>();
		File[] files = new File(backupLocation).listFiles();
		if (files != null) {
			for (File file : files) {
				if (file.isDirectory() && !file.getName().equals(RUNNING)
						&& !file.getName().equals(READY)) {
					File[] fs = file.listFiles();
					boolean isBackup = Boolean.TRUE;
					if (fs != null) {
						for (File f : fs) {
							// XXX
							if (FileLock.isLockFile(f.getAbsolutePath(),
									new LocalFile())) {
								isBackup = Boolean.FALSE;
							}
						}
					}
					
					if (isBackup) {
						try {
							list.add(new StandardDate(file.getName()));
						} catch (ParseException e) {
							warn("备份文件夹不是以时间为名，不能识别，删除文件夹'"
									+ file.getAbsolutePath() + "'.");
							FileUtil.clearFolder(file.getAbsolutePath());
							if (!file.delete()) {
								error("删除文件错误" + file.getAbsolutePath() + "'.");
							}
						}
					}
				}
			}
		}

		return list;
	}

	private void removeOldestFolder() {
		File[] files = new File(backupLocation).listFiles();
		if (files != null) {
			if (files.length >= backup) {

				long oldeset = System.currentTimeMillis();
				File file = null;
				int count = 0;
				for (File f : files) {
					if (!f.getAbsolutePath().endsWith(RUNNING)
							&& !f.getAbsolutePath().endsWith(READY)) {
						if (oldeset > f.lastModified()) {
							file = f;
							oldeset = f.lastModified();
						}
						count = count + 1;
					}

				}

				if (count >= backup) {
					info("备份数已达到最大，删除最旧的备份.");
					if (file != null) {
						info("删除备份索引'" + file.getAbsolutePath() + "'");
						if (!FileUtil.delFolder(file.getAbsolutePath())) {
							warn("删除最旧备份索引失败.");
						}
					} else {
						warn("删除最旧备份索引失败.");
					}
				}
			}
		}
	}

	public boolean canSearcherRead() {
		return !ready.isWriting() && !ready.isClearing();
	}

	private void doNothing(long ms) {
		try {
			Thread.sleep(ms);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public String getLocation() {
		if (!location.endsWith("/")) {
			location = location + "/";
		}
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}

	public String getBackupLocation() {
		if (!backupLocation.endsWith("/")) {
			backupLocation = backupLocation + "/";
		}
		return backupLocation;
	}

	public void setBackupLocation(String backupLocation) {
		this.backupLocation = backupLocation;
	}

	public int getBackup() {
		return backup;
	}

	public void setBackup(int backup) {
		this.backup = backup;
	}

	public String getReadyFolder() {
		return ready.getPath();
	}

	public String getRunningFolder() {
		return running.getPath();
	}

	private void info(String message) {
		EventLogger.info(new MonitedValue(message), EventLogger
				.getCurrentEventId(FluteMonitor.getIndexerKey()));
	}

	private void warn(String message) {
		EventLogger.warn(new MonitedValue(message), EventLogger
				.getCurrentEventId(FluteMonitor.getIndexerKey()));
	}

	private void error(String message) {
		EventLogger.error(new MonitedValue(message), EventLogger
				.getCurrentEventId(FluteMonitor.getIndexerKey()));
	}

	public IFileIndexCounter getCounter() {
		return counter;
	}

	public void setCounter(IFileIndexCounter counter) {
		this.counter = counter;
	}

}
